64비트 time_t와 32비트 int 변환 — 2038년 문제와 Coverity 정적 분석 대응

NOTE

64비트 시스템에서 time(NULL)(time_t, 8바이트)의 반환값을 32비트 int(4바이트) 변수에 대입할 때 발생하는 **2038년 문제(Year 2038 Problem)**와, 상용 정적 분석 도구(Coverity 등)의 결함 경고를 해결하는 실무 방어 코드(% INT_MAX)의 원리를 정리한다.


0. 문제의 코드 스니펫

실무 C 소스코드에서 다음과 같은 수정 내역을 자주 볼 수 있습니다:

#if 0 /* 과거 코드 */
    i_cur_time = time(NULL);
#else /* Coverity 정적 분석 결함 조치 코드 */
    i_cur_time = (int)(time(NULL) % INT_MAX);
#endif

단순히 현재 시간을 구하는 평범한 코드에 왜 굳이 % INT_MAX라는 나머지 연산과 (int) 캐스팅을 덕지덕지 붙였을까요?


1. 근본 원인: time_t(64비트) vs int(32비트)

64비트 리눅스(x86_64, AArch64) 환경에서 두 타입의 크기는 다릅니다:

타입바이트 크기비트 수표현 범위
time_t8바이트64비트기원전 ~ 수천억 년 후까지 표현 가능
int4바이트32비트 ~ (INT_MAX)
int i_cur_time = time(NULL); // ⚠️ 8바이트 ➡️ 4바이트 절삭 (Truncation)!

컴파일러 입장에서는 8바이트 정수의 상위 4바이트를 강제로 버리고 하위 4바이트만 int에 욱여넣는 **데이터 손실(Data Truncation)**이 발생합니다.


2. 2038년 문제(Year 2038 Problem, Y2038)

Unix 시간(Epoch)은 1970년 1월 1일 00:00:00 UTC를 기준(0초)으로 매초 1씩 증가합니다.

  • 32비트 부호 있는 정수(signed int)가 표현할 수 있는 최대 초 수는 **2,147,483,647초**입니다.
  • 이 한계에 도달하는 정확한 시점이 바로 2038년 1월 19일 03:14:07 UTC입니다!
2038년 1월 19일 03:14:07 UTC ──► time_t =  2,147,483,647 (32비트 최대값)
2038년 1월 19일 03:14:08 UTC ──► time_t = -2,147,483,648 (음수 오버플로 발생! 1901년으로 회귀!)

정적 분석 도구(Coverity, Fortify, SonarQube)의 판정

  • 금융/통신/방산 등 미션 크리티컬 시스템의 CI/CD 파이프라인에서는 소스코드 정적 분석기를 필수적으로 돌립니다.
  • 정적 분석기는 int t = time(NULL);을 보는 순간 “CWE-197: Numeric Truncation Error” 또는 **“CWE-190: Integer Overflow”**라는 최고 등급(High/Critical) 결함을 발행하고 배포 빌드를 즉각 차단합니다.

3. 실무 해결책 3가지와 % INT_MAX의 비밀

방법 A: 변수 타입을 time_t로 전면 변경 (가장 이상적이지만 제약 존재)

time_t cur_time = time(NULL); // 가장 표준적인 정석
  • 실무 한계: 수십 년간 축적된 레거시 시스템에서는 기존 통신 IPC 메시지 구조체나 공유 메모리(SHM) 구조체의 필드가 이미 int i_cur_time;으로 박제되어 있는 경우가 많습니다.
  • 구조체 필드 타입을 4바이트에서 8바이트로 바꾸면 구조체 전체 크기가 바뀌어 다른 프로세스와의 바이너리 호환성(ABI)이 깨지는 대참사가 발생합니다.

방법 B: 실무 패치 — (int)(time(NULL) % INT_MAX) (Coverity Silence 패턴)

기존의 int 변수 크기(4바이트)를 유지하면서 정적 분석기를 통과시키는 기법입니다.

동작 원리

  1. time(NULL)(64비트 정수)을 INT_MAX(32비트 최대 양수, 0x7FFFFFFF)로 나머지 연산(%)합니다.
  2. 수학적으로 어떤 큰 수를 INT_MAX로 나눈 나머지는 항상 0 ~ INT_MAX - 1 범위의 양수가 됩니다.
  3. 결과값이 무조건 int가 담을 수 있는 안전한 양수 범위임이 수학적으로 보장되므로, 정적 분석기(Coverity)는 “오버플로 위험이 제거되었다”고 판단하고 **결함 경고를 해제(Silencing)**합니다.

주기 검사(Heartbeat/Watchdog)에서의 안전성

이 코드가 주로 쓰이는 곳은 다음과 같은 주기적 경과 시간 검사입니다:

if (i_cur_time - i_update_time >= HANGUP_UPDATE_SEC) {
    oam_hangup_check_update(...);
    i_update_time = i_cur_time;
}
  • 10초, 30초 같은 “시간 간격(Delta)“만 계산하면 되는 로직에서는, 절대 시간이 INT_MAX(약 68년)마다 한 번 래핑(Wrapping)되더라도 실시간 경과 시간 계산에는 실질적인 부작용이 전혀 없습니다.

방법 C: uint32_t (부호 없는 정수) 사용

uint32_t u_cur_time = (uint32_t)time(NULL);
  • 부호 비트(1비트)를 시간 표현에 추가로 활용하여 한계를 2106년 2월 7일까지 연장할 수 있습니다.
  • 특히 경과 시간 계산(now - last >= INTERVAL) 시, C 언어 표준의 부호 없는 정수 래퍼라운드(Modulo Arithmetic) 규칙 덕분에 시간이 한 바퀴 돌아도 뺄셈 결과가 항상 정확한 양수 델타로 계산되는 장점이 있습니다.

4. 요약 및 실무 가이드

대응 방식코드 예시장점단점 / 사용처
정석 대응time_t now = time(NULL);2038년 문제 완벽 해결, C 표준 준수기존 32비트 구조체와 ABI 호환 깨짐
부호 없는 확장uint32_t now = (uint32_t)time(NULL);2106년까지 유효, 델타 계산 안전4바이트 구조체 필드 호환용
Coverity 패치(int)(time(NULL) % INT_MAX)기존 int 타입 유지, 정적분석 결함 즉시 패스68년 주기의 래핑 발생 (주기 검사용에 적합)

신규 코드를 작성할 때는 처음부터 시간 변수를 time_t 또는 **int64_t**로 선언하는 것이 원칙이며, 레거시 시스템의 32비트 구조체 호환과 정적 분석 통과가 동시에 필요할 때 위의 % INT_MAX 관용구가 사용됩니다.


관련 문서